﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Globalization;

namespace MovieRental.Charts
{
    public class TimeMoneyChart
    {
        private Canvas _chartCanvas;
        private TimeDataSeries<double> _dataSeries;

        private const string _currencyFormat = "{0:c}";
        private const string _dateFormat = "{0:m}";
        private const double _xOffsetCorrection = 5.0;
        private const double _yOffsetCorrection = 20.0;
        private const double _topOffset = 5.0;
        private const double _gridLabelCorrection = 5.0;

        public TimeDataSeries<double> DataSeries
        {
            private get
            {
                return _dataSeries;
            }
            set
            {
                _dataSeries = value;
                Draw();
            }
        }

        public CultureInfo Culture { get; set; }

        public Color ChartColor { get; set; }

        public double ChartThickness { get; set; }

        public Color GridLinesColor { get; set; }

        public DoubleCollection GridLinesDashArray { get; set; }

        public TimeMoneyChart()
        {
            GridLinesColor = Colors.White;
            GridLinesDashArray = new DoubleCollection() { 2 };
            ChartColor = Colors.Red;
            ChartThickness = 3;
            Culture = new CultureInfo("en-US");
        }

        private void Draw()
        {
            if (DataSeries == null || DataSeries.Points.Count() == 0)
                return;
            _chartCanvas.Children.Clear();
            Polyline plot = new Polyline();
            plot.Stroke = new SolidColorBrush(ChartColor);
            plot.StrokeThickness = ChartThickness;
            DrawXGridLines();
            DrawYGridLines();
            foreach (var item in DataSeries.Points)
                plot.Points.Add(CalculatePoint(item.Key, item.Value));
            _chartCanvas.Children.Add(plot);
        }

        private void DrawYGridLines()
        {
            int steps = (int) ((_chartCanvas.ActualHeight - YOffset) / GetYAxeLabelDesiredSize().Height / 2);
            int k = (int) ((DataSeries.MaxValue - DataSeries.MinValue) / steps);
            double valueStep = (k / 10 + 1) * 10.0;
            double val = (int)DataSeries.MinValue / 10;
            while (val <= NormalizedMaxValue)
            {
                DrawYGridLineAndLabel(val);
                val += valueStep;
            }
        }

        private void DrawXGridLines()
        {
            int steps = (int)((_chartCanvas.ActualWidth - XOffset) / GetXAxeLabelDesiredSize(DataSeries.MaxTime).Width / 2);
            int valueStep = (DataSeries.MaxTime - DataSeries.MinTime).Days / steps;
            valueStep = (valueStep == 0) ? 1 : valueStep;
            DateTime val = DataSeries.MinTime;
            while (val <= DataSeries.MaxTime)
            {
                DrawXGridLineAndLabel(val);
                val = val.AddDays(valueStep);
            }
        }

        private void DrawYGridLineAndLabel(double value)
        {
            TextBlock yLabel = new TextBlock();
            yLabel.Foreground = new SolidColorBrush(GridLinesColor);
            yLabel.Text = string.Format(Culture, _currencyFormat, value);
            Canvas.SetTop(yLabel, CalculateY(value) - GetYAxeLabelDesiredSize().Height / 2 - _gridLabelCorrection);
            Canvas.SetLeft(yLabel, 0);
            Line gridLine = new Line();
            gridLine.X1 = XOffset;
            gridLine.X2 = _chartCanvas.ActualWidth;
            gridLine.Y1 = CalculateY(value);
            gridLine.Y2 = gridLine.Y1;
            gridLine.Stroke = new SolidColorBrush(GridLinesColor);
            gridLine.StrokeDashArray = GridLinesDashArray;
            _chartCanvas.Children.Add(gridLine);
            _chartCanvas.Children.Add(yLabel);
        }

        private void DrawXGridLineAndLabel(DateTime date)
        {
            TextBlock xLabel = new TextBlock();
            xLabel.Foreground = new SolidColorBrush(GridLinesColor);
            xLabel.Text = string.Format(Culture, _dateFormat, date);
            Canvas.SetTop(xLabel, _chartCanvas.ActualHeight - GetXAxeLabelDesiredSize(date).Height);
            double desiredLeft = CalculateX(date) - GetXAxeLabelDesiredSize(date).Width / 2;
            double left = (desiredLeft + GetXAxeLabelDesiredSize(date).Width >= _chartCanvas.ActualWidth) 
                ? _chartCanvas.ActualWidth - GetXAxeLabelDesiredSize(date).Width + 10
                : desiredLeft;
            Canvas.SetLeft(xLabel, left);
            Line gridLine = new Line();
            gridLine.X1 = CalculateX(date);
            gridLine.X2 = gridLine.X1;
            gridLine.Y1 = CalculateY(NormalizedMaxValue);
            gridLine.Y2 = CalculateY(DataSeries.MinValue);
            gridLine.Stroke = new SolidColorBrush(GridLinesColor);
            gridLine.StrokeDashArray = GridLinesDashArray;
            _chartCanvas.Children.Add(gridLine);
            _chartCanvas.Children.Add(xLabel);
        }

        private Size GetYAxeLabelDesiredSize()
        {
            TextBlock tb = new TextBlock();
            tb.Text = string.Format(_currencyFormat, DataSeries.MaxValue);
            tb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            return tb.DesiredSize;
        }

        private Size GetXAxeLabelDesiredSize(DateTime time)
        {
            TextBlock tb = new TextBlock();
            tb.Text = string.Format(_dateFormat, time);
            tb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            return tb.DesiredSize;
        }

        private double XOffset
        {
            get { return GetXAxeLabelDesiredSize(DataSeries.MaxTime).Width + _xOffsetCorrection; }
        }

        private double YOffset
        {
            get { return GetYAxeLabelDesiredSize().Height + _yOffsetCorrection; }
        }

        private double XScale
        {
            get { return (_chartCanvas.ActualWidth - XOffset) / (DataSeries.MaxTime - DataSeries.MinTime).Days; }
        }

        private double YScale
        {
            get { return (_chartCanvas.ActualHeight - YOffset - _topOffset) / (NormalizedMaxValue - DataSeries.MinValue); }
        }

        private double NormalizedMaxValue
        {
            get { return ((int)DataSeries.MaxValue / 10 + 1) * 10.0; }
        }


        private Point CalculatePoint(DateTime time, double value)
        {
            Point result = new Point();
            result.X = CalculateX(time);
            result.Y = CalculateY(value);
            return result;
        }

        private double CalculateX(DateTime time)
        {
            return (time - DataSeries.MinTime).Days * XScale + XOffset;
        }

        private double CalculateY(double value)
        {
            return _chartCanvas.ActualHeight - YOffset - (value - DataSeries.MinValue) * YScale + _topOffset;
        }

        public Canvas ChartCanvas
        {
            get
            {
                return _chartCanvas;
            }
            set
            {
                _chartCanvas = value;
                _chartCanvas.SizeChanged += OnCanvasSizeChanged;
                Draw();
            }
        }

        void OnCanvasSizeChanged(object sender, SizeChangedEventArgs args)
        {
            Draw();
        }



    }
}
